/* log.c
 * This file is part of the Tisa Project.
 * 
 * Authors: Cleiber Marques da Silva <cleiber.dev@gmail.com>
 *          Odilson Tadeu Valle <odilson@sj.cefetsc.edu.br>
 *
 * Tisa Project is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; version 2 of
 * the License.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <sys/uio.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>
#include <pthread.h>

#include "log.h"

static volatile int killed = 0;
static pthread_mutex_t mutex;
static pthread_cond_t cond;
static pthread_t log_runner;
static struct buffer *ptr;
static FILE *log;
int size = BUFFER_SIZE;

static const char *log_get_level_string(int level)
{
	switch (level) {
	case LOG_ERROR:
		return "ERROR";
	case LOG_WARNING:
		return "WARNING";
	case LOG_NOTICE:
		return "NOTICE";
	case LOG_DEBUG:
		return "DEBUG";
	default:
		return "*UNKNOWN*";
	}
}

void log_print(const int level, const char *fmt, ...)
{
	int res, i;
	va_list ap;
	char s[1024];
	struct timeval currtime;
	struct tm *currtime_tm;
	time_t currsec = time(NULL);

	if ((res = gettimeofday(&currtime, NULL)) < 0) {
		fprintf(stderr, "gettimeofday failed!\n");
		return;
	}
	if (!(currtime_tm = localtime(&currsec))) {
		fprintf(stderr, "localtime failed!\n");
		return;
	}

	va_start(ap, fmt);	
	i = sprintf(s, "[%02d:%02d:%02d:%03lu][%s] - ", currtime_tm->tm_hour, currtime_tm->tm_min, currtime_tm->tm_sec, currtime.tv_usec/1000, log_get_level_string(level));
	i += vsprintf(s + i, fmt, ap);
	va_end(ap);
	
	pthread_mutex_lock(&mutex);
	strncat(ptr->data, s, size);

	if (i >= size) { /* buffer full */
		strncat(ptr->next->data, s + size, i - size);
		ptr->ready = 1;
		pthread_cond_signal(&cond);
		size = BUFFER_SIZE - ((i - size));
	}
	else 
		size -= i;

	pthread_mutex_unlock(&mutex);
}

/* log_print_buffer - print double buffer in file */
static void *log_print_buffer(void *arg)
{
	log_print(LOG_NOTICE, "thread log_print_buffer started\n");

	pthread_mutex_lock(&mutex);
	for(;;) {
		char *buffer;
		if (killed)
			break;
		if(!ptr->ready)
			pthread_cond_wait(&cond, &mutex);
		buffer = ptr->data;
		ptr->ready = 0;
		ptr = ptr->next;

		/* writes the file */
		pthread_mutex_unlock(&mutex);
		fprintf(log, "%s", buffer);
		*buffer = 0; /* EOF */
		fflush(log);
		pthread_mutex_lock(&mutex);
	}
	pthread_mutex_unlock(&mutex);
	pthread_exit(NULL);
}

int log_init(const char *path)
{
	pthread_mutexattr_t mattrs;
	struct buffer *aux;
	FILE *fp;
	int i;

	/* create circular list for double buffer */
	if (!(aux = ptr = malloc(sizeof(struct buffer)))) {
		fprintf(stderr, "malloc double buffer failed!");
		return -1;
	}
	memset(aux, 0, sizeof(struct buffer));
	aux->id = 0;

	for (i = 1; i < BUFFER_NUM; i++) {
		if (!(aux->next = malloc(sizeof(struct buffer)))) {
			fprintf(stderr, "malloc double buffer failed!");
			return -1;
		}
		aux = aux->next;
		memset(aux, 0, sizeof(struct buffer));
		aux->id = i;
		aux->next = ptr;
	}

	pthread_mutexattr_init(&mattrs);
	pthread_mutexattr_settype(&mattrs, PTHREAD_MUTEX_FAST_NP);
	if (pthread_mutex_init(&mutex, &mattrs) ||
		pthread_cond_init(&cond, NULL) ||
		pthread_create(&log_runner, NULL, log_print_buffer, NULL))
		return -1;

	if (!path) {
		log = stderr;
		return 0;
	}

	fp = fopen(path, "a");
	if (!fp) {
		log = stderr;
		return 0;
	}
	log = fp;
	return 0;
}

void log_close(void)
{
	killed = 1;
	pthread_cond_signal(&cond);
	pthread_join(log_runner, NULL);
	/* XXX TODO free for double buffer */
	if (log && log != stderr)
		fclose(log);
	log = stderr;

	
}
